package com.gitee.huanminabc.fastboot.load.config;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.gitee.huanminabc.jcommon.file.FileReadUtil;
import com.gitee.huanminabc.jcommon.file.FileUtil;
import com.gitee.huanminabc.jcommon.file.IoStreamUtil;
import com.gitee.huanminabc.jcommon.reflect.BeanCopyUtil;
import com.gitee.huanminabc.jcommon.str.StringUtil;
import com.gitee.huanminabc.fastboot.context.AppContext;
import com.gitee.huanminabc.fastboot.load.hook.LoadAppConfigFinishLifecycleHook;
import com.gitee.huanminabc.nullchain.Null;
import com.gitee.huanminabc.nullchain.core.NullChain;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @description:
 * @author: huanmin
 * @create: 2025-04-07 12:39
 **/
@Slf4j
public class AppSystemConfigLoad {

    public static void start() {
        try {
            AppContext.taskTimeWatch.start("AppSystemConfigLoad耗时");
            //主配置加载
            loadAppConfigMain();
            //子配置加载
            envLoadAppConfig();
            //应用配置
            appBaseInfoConfigConfig();
            //日志配置
            logbackConfig();
            //配置加载完成钩子
            new LoadAppConfigFinishLifecycleHook().run();

        } catch (Exception e) {
            log.error("AppSystemConfigLoad load error", e);
            System.exit(1);
        } finally {
            AppContext.taskTimeWatch.stop();
        }
        log.debug("AppSystemConfigLoad load success  size:{}", AppContext.appConfigMap.size());
    }


    private static void envLoadAppConfig() {
        //拿到配置里面的
        Set<String> profiles = AppContext.profiles;
        if (profiles.isEmpty()) {
            return;
        }
        //读取配置文件application-{profile}.json  然后覆盖之前的配置  ,根据profiles的顺序覆盖
        Map<String, JSONObject> reduce = profiles.parallelStream().map((profile) -> {
            Map<String, JSONObject> mapStream = new HashMap<>();
            JSONObject jsonObjectStream = loadAppConfigChild(profile);
            mapStream.put(profile, jsonObjectStream);
            return mapStream;
        }).reduce(new HashMap<>(), (map1, map2) -> {
            map1.putAll(map2);
            return map1;
        });
        //合并配置 后面的配置覆盖前面的配置
        for (String profile : profiles) {
            JSONObject jsonObject = reduce.get(profile);
            if (jsonObject != null) {
                //合并配置
                AppContext.appConfigMap.putAll(jsonObject);
            }
        }

    }

    private static void appBaseInfoConfigConfig() {
        //读取servlet配置
        JSONObject application = AppContext.appConfigMap.getJSONObject("application");
        if (application != null) {
            //转换为appBaseInfoConfig
            AppBaseInfoConfig newAppBaseInfoConfig = JSON.parseObject(JSON.toJSONString(application), AppBaseInfoConfig.class);
            BeanCopyUtil.copyIgnoreNull(newAppBaseInfoConfig, AppContext.appBaseInfoConfig, "web");
            Null.of(System.getProperty("application.name")).ifPresent(AppContext.appBaseInfoConfig::setName);
        }

    }

    private static void logbackConfig() throws JoranException {
        //日志配置
        JSONObject loggingMap = AppContext.appConfigMap.getJSONObject("logging");
        if (loggingMap != null) {
            // 设置 logging.env 系统属性
            Null.of(System.getProperty("logging.path")).or(loggingMap.getString("path")).ifPresent(value -> System.setProperty("logging.path", value));
            Null.of(System.getProperty("logging.level")).or(loggingMap.getString("level")).ifPresent(value -> System.setProperty("logging.level", value));
            Null.of(System.getProperty("logging.pattern")).or(loggingMap.getString("pattern")).ifPresent(value -> System.setProperty("logging.pattern", value));
            Null.of(AppContext.appBaseInfoConfig.getName()).ifPresent(value -> System.setProperty("logging.contextName", value));
            LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
            JoranConfigurator configurator = new JoranConfigurator();
            configurator.setContext(loggerContext);
            loggerContext.reset();

            String env = Null.of(System.getProperty("logging.env"))
                    //log配置优先于系统配置
                    .or(loggingMap.getString("env"))
                    .or(AppContext.profile)
                    .orElse("dev");

            String fileName = "logback-" + env + ".xml";
            String filePath = findAppConfigPath(fileName);
            if (filePath == null) {
                log.error("load logback config file not found: {}", filePath);
                System.exit(1);
            }
            //判断file是否是jar包里面的,如果是那么需要通过classloader获取
            //如果不是jar包里面的,那么直接读取文件
            if (filePath.contains(".jar!")) {
                //重新加载配置
                InputStream resourceAsStream = AppConfigUtil.class.getClassLoader().getResourceAsStream(fileName);
                configurator.doConfigure(resourceAsStream);
            } else {
                //如果不是jar包里面的,那么直接读取文件
                File file = new File(filePath);
                configurator.doConfigure(file);

            }
            log.debug("load logback config file: {}", filePath);
        }
    }

    //获取profiles中第一个环境作为系统的环境
    private static String getFirstProfile() {
        Set<String> profiles = AppContext.profiles;
        if (profiles.isEmpty()) {
            return "dev";
        }
        return profiles.iterator().next();
    }

    private static void loadAppConfigMain() {
        String fileName = "application.json";
        String filePath = findAppConfigPath(fileName);
        log.debug("load app config file: {}", filePath);
        if (filePath != null) {
            JSONObject map = getConfigContent(fileName, filePath);
            AppContext.appConfigMap.putAll(map);
        }
        //多环境配置加载
        JSONArray profiles = AppContext.appConfigMap.getJSONArray("profiles");
        if (profiles != null) {
            AppContext.profiles.addAll(profiles.toJavaList(String.class));
            AppContext.profile = getFirstProfile(); //获取第一个环境
            NullChain<String[]> nullChain = Null.of(System.getProperty("profiles"))
                    //如果通过外部参数指定的那么就清除配置文件中的参数采用外部的参数
                    .then(AppContext.profiles::clear)
                    .map(StringUtil::splitComma);
            Null.ofStreamArray(nullChain).forEach(AppContext.profiles::add);
        }
    }

    //读取配置文件 ,获取resources目录下application.json
    private static JSONObject loadAppConfigChild(String profile) {
        String fileName = "application-" + profile + ".json";
        String file = findAppConfigPath(fileName);
        if (file == null) {
            log.warn("{} not found", fileName);
            return null;
        }
        log.debug("load app config file: {}", file);
        JSONObject map = getConfigContent(fileName, file);
        //排除servlet配置,不覆盖主配置
        map.remove("servlet");
        return map;
    }

    private static JSONObject getConfigContent(String fileName, String filePath) {
        //读取文件内容
        String content = null;
        //判断file是否是jar包里面的,如果是那么需要通过classloader获取
        if (filePath.contains(".jar!")) {
            //如果是jar包里面的,那么需要通过classloader获取
            try (InputStream resourceAsStream = AppConfigUtil.class.getClassLoader().getResourceAsStream(fileName)) {
                if (resourceAsStream == null) {
                    throw new RuntimeException(filePath + " not found");
                }
                //读取文件内容
                content = IoStreamUtil.inputStreamToStr(resourceAsStream);
            } catch (IOException e) {
                log.error(fileName + "  read error", e);
                System.exit(1);
            }
        } else {
            //如果不是jar包里面的,那么直接读取文件
            content = FileReadUtil.readAllStr(new File(filePath));
        }
        //合并配置 , 后面的配置覆盖前面的配置
        return JSON.parseObject(content);
    }

    //寻找配置文件
    //1.在找config/application.json 如果是jar包则在jar包同级目录下
    //2.在找项目根目录下的application.json 如果是jar包则在jar包同级目录下
    //3.先找resources目录下的application.json , 也就是classpath下的application.json
    //如果在System.getProperty("application.config.path")有值优先级最高
    public static String findAppConfigPath(String fileName) {
        //-Dapp.config.path=/path/xxx
        String path = System.getProperty("application.config.path");
        if (path != null) {
            File file = Paths.get(path, fileName).toFile();
            if (file.exists()) {
                return path;
            }
        }
        //config/application.json
        String configPath = System.getProperty("user.dir") + File.separator + "config" + File.separator + fileName;
        if (FileUtil.isFile(configPath)) {
            return configPath;
        }

        //项目根目录下的application.json
        String rootPath = System.getProperty("user.dir") + File.separator + fileName;
        if (FileUtil.isFile(rootPath)) {
            return rootPath;
        }

        URL resource = AppConfigUtil.class.getClassLoader().getResource(fileName);
        if (resource != null) {
            return resource.getFile();
        }
        return null;
    }
}
